package thread;

//生产者消费者

public class Test17_producerConsumer {
    public static void main(String[] args) {
        AppleBox appleBox = new AppleBox();
        Producer p = new Producer(appleBox);
        Producer p1 = new Producer(appleBox);
        Producer p2 = new Producer(appleBox);

        Consumer c = new Consumer(appleBox);

        new Thread(p).start();
        new Thread(p1).start();
        new Thread(p2).start();

        new Thread(c).start();
    }
}

//待处理的消息
class Apple{
    String id;
    Apple(String id){
        this.id = id;
    }

    @Override
    public String toString() {
        return "apple:"+id;
    }
}

//中间缓存区
class AppleBox{
    int index = 0;
    Apple[] apples = new Apple[5];

    //存消息
    /**
     * 问题：
     * 1.下标越界 while
     *      如果使用if对index == apples.length进行判断，那么只会判断一次。
     *      但如果当线程在等待过程中，没有消费者对产品进行消费的话，
     *      index == apples.length的条件依旧成立，
     *      但是线程会继续往下执行index++，导致下标越界
     * 2.生产的内容重复放在一个index中
     *      因为没有实现加锁，所以可能存在一种情况：
     *      当index=a(a是一个定值)时，
     *      一个线程进入deposite，将生产的apple放入apples[a]
     *      然后cpu时间片结束；另一个线程也进入deposite，
     *      将生产的apple也放入apples[a]；这时，两个线程都将生产的产品放在了同一个位置，
     *      后一个覆盖前一个，导致数据“消失”
     * 3.使用sleep不会释放锁
     *      在index == apples.length条件成立后，仓库满了，
     *      生产线程等待需消费者消费产品，但是如果使用sleep，
     *      线程不会释放锁，会导致消费线程一直拿不到锁而无法消费，
     *      应使用wait替代它
     * 4.使用wait后，线程进入等待状态，但是无法被唤醒，会导致线程假死
     *      每当有产品被消费后，需要唤醒正在等待此对象监视器锁的所有线程。
     *      需调用notifyAll()方法。
     *
     */

    //synchronized解决
    public synchronized void deposite(Apple apple){
        while (index == apples.length){
            try {
                this.wait();
            }catch (InterruptedException e){
                e.printStackTrace();
            }
        }
        this.notifyAll();
        apples[index] = apple;
        index++;
    }
    //取消息
    public synchronized Apple withdraw(){
        while (index == 0){
            try {
                this.wait();
            }catch (InterruptedException e){
                e.printStackTrace();
            }
        }
        this.notifyAll();
        index--;
        return apples[index];
    }
}

class Producer implements Runnable{
    AppleBox appleBox = null;
    Producer(AppleBox appleBox){
        this.appleBox = appleBox;
    }

    @Override
    public void run() {
        for (int i=0;i<5;i++){
            //创建消息
            Apple a = new Apple(Thread.currentThread().getName()+"牌："+i);
            appleBox.deposite(a);
            System.out.println(Thread.currentThread().getName()+"生产了:"+a);
            try {
                Thread.sleep((int)(Math.random()*1000));
            } catch (InterruptedException e) {
                throw new RuntimeException(e);
            }
        }
    }
}

class Consumer implements Runnable{
    AppleBox appleBox = null;
    Consumer(AppleBox appleBox){
        this.appleBox = appleBox;
    }

    @Override
    public void run() {
        while (appleBox.index>0){
            //只要appleBox中有产品就一直消费
            while (appleBox.index>0){
                Apple a = appleBox.withdraw();
                System.out.println(Thread.currentThread().getName()+"消费了:"+a);
                try {
                    Thread.sleep((int)(Math.random()*1000));
                } catch (InterruptedException e) {
                    throw new RuntimeException(e);
                }
            }
        }
    }
}